#include <stdio.h>

//ntohl,ntohs,htonl,htons,函数本身不做大小端判断，即重复使用同一函数两次，相当于没有转
//INET_ADDRSTRLEN，INET6_ADDRSTRLEN
#include <netinet/in.h>

#include <arpa/inet.h> // 字符串类型ip转整数类型ip
#include <bits/socket.h>//协议族，AF_INET

#include <assert.h>

#include <sys/types.h>
#include <sys/socket.h> // socket api

#include <unistd.h> //close

#include <strings.h> //bzero

#include <string.h> //strlen strcmp

#include <stdlib.h> // atoi

#include <sys/select.h>

#include <poll.h>



const char strIP4[] = "192.168.242.134";
int maxfd = 0;




//telnet 192.168.242.134 8088
//netstat -nt | grep 8088




union endint
{
    int littleEndian;
    unsigned char b;
};


void testEndian()
{
    union endint test;
    test.littleEndian = 0x01020304;
    printf("num : %08x\n", test.littleEndian);

//    test.littleEndian = htonl(test.littleEndian);//转为大端

    if (1 == test.b)
    {
        printf("this is big endian\n");
    }
    else if (4 == test.b)
    {
        printf("this is little endian\n");
    }
    else
    {
        printf("unkonw endlian\n");
    }
}


void runCli(const char* strip, int port)
{
    //创建socket
    int clientSocketfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(clientSocketfd > -1);

    //发起连接
    struct sockaddr_in serverAddr;
    bzero(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    inet_pton(AF_INET, strip, &serverAddr.sin_addr);
    serverAddr.sin_port = htons(port);
    int ret = connect(clientSocketfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));//阻塞函数
    assert(0 == ret);

    const char* sendbuf = "i am client\n";
    char recvbuf[1024];

    send(clientSocketfd, sendbuf, strlen(sendbuf), 0);
//    recv(clientSocketfd, recvbuf, 1024, 0);
//    printf("recv msg from ser:%s\n", recvbuf);

    while (1)
    {
        char strBuf[1024];
        printf("please enter some text\n");
        gets(strBuf);
        send(clientSocketfd, strBuf, strlen(sendbuf), 0);
    }

    //关闭
    shutdown(clientSocketfd, SHUT_RDWR);
}



int main(int argc, char* argv[])//argv第一个参数是文件名，后面是自己加的
{
//    testEndian();


    if (argc < 4)
    {
        printf("\nparm count little\n");
    }

    const char* role = argv[1];//cli or ser
    const char* strip = argv[2];
    int port = atoi(argv[3]);

    if (0 == strcmp(role, "cli"))
    {
        runCli(strip, port);
        printf("\nclient close\n");
        return 0;
    }
    else if ((0 == strcmp(role, "ser")))
    {
        printf("server is running\n");
    }


    //字符串ip转换
    uint32_t ipvt4;
    int ret = inet_pton(AF_INET, strIP4, &ipvt4);//字符串ip转换为大端整数表示
    assert(1 == ret);
    ipvt4 = ntohl(ipvt4);
    printf("the ip num is %08x\n", ipvt4);

    char stripBuf[INET_ADDRSTRLEN];
    const char* addr = inet_ntop(AF_INET, &ipvt4, stripBuf, INET_ADDRSTRLEN);//大端整数转为字符串
    assert(NULL != addr);

    printf("str ip : %s\n", stripBuf);


    //use socket
    {

    //创建socket
    int listenSocketfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenSocketfd > -1);

    if (listenSocketfd > maxfd) maxfd = listenSocketfd;

    //命名，客户端通常不需要命名，服务端需要
    struct sockaddr_in addr;
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, strip, &addr.sin_addr);
    addr.sin_port = htons(port);

    int ret = bind(listenSocketfd, (struct sockaddr*)&addr, sizeof(addr));
    assert(-1 != ret);//EADDRINUSE

    //监听
    ret = listen(listenSocketfd, 5);
    assert(0 == ret);

    //poll使用
    {

//        在使用方式上，先调用 FD_ZERO 将 fd_set 清零，然后调用 FD_SET 把需要监测的文件描述符加入 fd_set，接着调用函数 select 测试 fd_set 中的所有文件描述符，
//        select 函数返回后，用宏 FD_ISSET 检查某个文件描述符对应的比特位是否仍然为 1，如果为 1 表示有事件就绪，为 0 则反之。

//        理解，每次需要将所有文件描述FD_SET，有就绪文件描述符后，内核将其余未就绪的置零，然后应用层用FD_ISSET检查
//        所以每次select返回后，需要将未就绪的文件描述重新FD_SET；或者在进入select，使用临时的fd_set

//        poll的使用根sellect类似
//        使用前先初始化pollfd，然后将需要的文件描述符加入pollfd进行监听，
//        poll返回后，处理已经就绪的文件描述符号，然后需要将该文件描述符号的就绪revents置0


        //poll的监听数组
        struct pollfd pollfds[1024];
        //初始化
        for(int i = 0;i<1024;i++)
        {
            pollfds[i].fd = -1;
            pollfds[i].events = 0;
            pollfds[i].revents = 0;
        }

        //监听socket的可读事件
        pollfds[listenSocketfd].fd = listenSocketfd;
        pollfds[listenSocketfd].events = POLLIN;
        pollfds[listenSocketfd].revents = 0;

        while (1)
        {
            int readyfdCount = poll(pollfds,  1024, -1);//__timeout为-1，一直阻塞，直到有就绪

            if (-1 == readyfdCount)
            {
                printf("select occur err\n");
            }
            else if (0 == readyfdCount)
            {
                printf("timeout but no ready fd\n");
            }
            else
            {
                int dealCount = 0;
                    for (int fd = 0; fd <= maxfd; ++fd)//查找到底是那些文件描述符号就绪
                    {
                        if (pollfds[fd].revents & POLLIN)
                        {
                            if (fd == listenSocketfd)//监听socket就绪，说明有新的客户端连接过来
                            {
                                struct sockaddr_in clientAddr;
                                socklen_t clientAddrLen = sizeof(clientAddr);
                                int clientSocketfd = accept(listenSocketfd, (struct sockaddr*)&clientAddr, &clientAddrLen);//阻塞函数
                                assert(-1 != clientSocketfd);

                                if (clientSocketfd > -1)
                                {
                                    char strClientip[INET_ADDRSTRLEN];

                                    printf("client conn succ ip:%s and port:%d\n"
                                           , inet_ntop(AF_INET, &clientAddr.sin_addr, strClientip, INET_ADDRSTRLEN)
                                           , ntohs(clientAddr.sin_port));

                                    //更新最大文件描述符
                                    if (clientSocketfd > maxfd) maxfd = clientSocketfd;

                                    //将clientSocketfd加入监听
//                                    FD_SET(clientSocketfd, &readfds);//监听可读事件
                                    pollfds[clientSocketfd].fd = clientSocketfd;
                                    pollfds[clientSocketfd].events = POLLIN;
                                    pollfds[clientSocketfd].revents = 0;
                                }
                            }
                            else if (fd < 3)
                            {
                                //                            标准输入（stdin）：标准输入的文件描述符为0。它通常连接到终端，是程序接收输入数据的地方。
                                //                            标准输出（stdout）：标准输出的文件描述符为1。它通常连接到终端，是程序输出数据的地方。
                                //                            标准错误（stderr）：标准错误的文件描述符为2。它也通常连接到终端，用于输出程序执行期间发生的错误信息。
                            }
                            else//连接socket就绪，有客户端发来消息
                            {
                                char recvbuf[1024];
                                memset(recvbuf, '\0', sizeof(recvbuf));

                                int ret = recv(fd, recvbuf, 1024, 0);
                                printf("recv msg from cli:%s\n", recvbuf);

                                if (0 == ret)//对于客户端主动断开连接，会触发文件描述符号可读，所以需要针对该情况进行处理，这里只是简单的取消监听
                                {
                                    //取消监听
                                    pollfds[fd].fd = -1;
                                    pollfds[fd].events = 0;
                                    pollfds[fd].revents = 0;

                                    shutdown(fd, SHUT_RDWR);
                                }
                            }

                            //清除内核设置的事件标志
                            pollfds[fd].revents = 0;

                            ++dealCount;
                            if (dealCount == readyfdCount) break;//避免多余的遍历
                        }
                    }
            }
        }
    }


    //关闭
//    close(listenSocketfd);
    shutdown(listenSocketfd, SHUT_RDWR);
    }



    printf("server close\n");
    return 0;
}
